@alleyboss/micropay-solana-x402-paywall 3.3.15 → 3.5.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 +113 -0
- package/dist/agent/index.cjs +432 -0
- package/dist/agent/index.d.cts +77 -1
- package/dist/agent/index.d.ts +77 -1
- package/dist/agent/index.js +433 -2
- package/dist/fetch/index.cjs +421 -0
- package/dist/fetch/index.d.cts +403 -0
- package/dist/fetch/index.d.ts +403 -0
- package/dist/fetch/index.js +402 -0
- package/dist/index.cjs +432 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +432 -1
- package/package.json +27 -3
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ npm install @alleyboss/micropay-solana-x402-paywall @x402/core @x402/svm @solana
|
|
|
21
21
|
|
|
22
22
|
| Feature | Description | Status |
|
|
23
23
|
|---------|-------------|--------|
|
|
24
|
+
| 🔥 **x402Fetch** | Drop-in `fetch()` replacement with auto-payment | ✅ **NEW in v3.4** |
|
|
24
25
|
| 💰 **SOL & USDC** | Native SOL and SPL tokens (USDC, USDT) | ✅ Verified by `@x402/svm` |
|
|
25
26
|
| 🔐 **x402 Protocol** | Full HTTP 402 compliance | ✅ Powered by `@x402/core` |
|
|
26
27
|
| 🌐 **PayAI Format** | Multi-chain payment format support | ✅ Built-in |
|
|
@@ -33,6 +34,96 @@ npm install @alleyboss/micropay-solana-x402-paywall @x402/core @x402/svm @solana
|
|
|
33
34
|
| 📦 **Versioned Tx** | v0 Transaction support | ✅ Native (x402 SDK) |
|
|
34
35
|
| 🌳 **Tree-Shakeable** | Modular exports | ✅ Built-in |
|
|
35
36
|
|
|
37
|
+
## 🔥 x402Fetch — The Killer Feature (NEW in v3.4)
|
|
38
|
+
|
|
39
|
+
Replace `fetch()` with `x402Fetch()` and **402 responses are handled automatically**.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createX402Fetch } from '@alleyboss/micropay-solana-x402-paywall/fetch';
|
|
43
|
+
import { useWallet } from '@solana/wallet-adapter-react';
|
|
44
|
+
|
|
45
|
+
// Create configured fetch instance
|
|
46
|
+
const x402Fetch = createX402Fetch({
|
|
47
|
+
wallet: useWallet(), // Browser wallet adapter
|
|
48
|
+
network: 'mainnet-beta',
|
|
49
|
+
onPaymentRequired: async (req) => {
|
|
50
|
+
return confirm(`Pay ${req.amount} lamports to ${req.payTo}?`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Use it like fetch — that's it!
|
|
55
|
+
const response = await x402Fetch('https://api.example.com/premium-data');
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Server-Side / AI Agent Usage
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { createX402Fetch } from '@alleyboss/micropay-solana-x402-paywall/fetch';
|
|
63
|
+
import { Keypair } from '@solana/web3.js';
|
|
64
|
+
import bs58 from 'bs58';
|
|
65
|
+
|
|
66
|
+
const agentKeypair = Keypair.fromSecretKey(
|
|
67
|
+
bs58.decode(process.env.AGENT_PRIVATE_KEY!)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const x402Fetch = createX402Fetch({
|
|
71
|
+
wallet: agentKeypair, // Server-side keypair
|
|
72
|
+
network: 'mainnet-beta',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Autonomous payment — no user interaction needed
|
|
76
|
+
const response = await x402Fetch('https://api.example.com/ai-data');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Error Handling
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { X402PaymentError, isUserRejection } from '@alleyboss/micropay-solana-x402-paywall/fetch';
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await x402Fetch('/api/premium');
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (isUserRejection(error)) {
|
|
88
|
+
console.log('User declined payment');
|
|
89
|
+
} else if (error instanceof X402PaymentError) {
|
|
90
|
+
console.log('Payment failed:', error.code);
|
|
91
|
+
// Codes: USER_REJECTED, INSUFFICIENT_BALANCE, TRANSACTION_FAILED, etc.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Security Configuration (v3.4.1)
|
|
97
|
+
|
|
98
|
+
Protect your wallet from malicious 402 responses:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const x402Fetch = createX402Fetch({
|
|
102
|
+
wallet,
|
|
103
|
+
network: 'mainnet-beta',
|
|
104
|
+
|
|
105
|
+
// 🔒 Critical: Maximum payment per request (prevents wallet drain)
|
|
106
|
+
maxPaymentPerRequest: BigInt(10_000_000), // Max 0.01 SOL
|
|
107
|
+
|
|
108
|
+
// 🔒 Critical: Whitelist of allowed recipients (prevents phishing)
|
|
109
|
+
allowedRecipients: ['7fPjN...', 'ABC123...'],
|
|
110
|
+
|
|
111
|
+
// ⚡ UX: Transaction speed (processed=fast, finalized=safe)
|
|
112
|
+
commitment: 'confirmed', // 'processed' | 'confirmed' | 'finalized'
|
|
113
|
+
|
|
114
|
+
// 🛡️ Rate limiting (prevents infinite loops)
|
|
115
|
+
rateLimit: {
|
|
116
|
+
maxPayments: 10, // Max 10 payments
|
|
117
|
+
windowMs: 60_000, // Per minute
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Security Error Codes:**
|
|
123
|
+
- `AMOUNT_EXCEEDS_LIMIT` — Payment blocked: amount exceeds `maxPaymentPerRequest`
|
|
124
|
+
- `RECIPIENT_NOT_ALLOWED` — Payment blocked: recipient not in whitelist
|
|
125
|
+
- `RATE_LIMIT_EXCEEDED` — Payment blocked: too many payments in time window
|
|
126
|
+
|
|
36
127
|
## 📦 Quick Example (Express.js)
|
|
37
128
|
|
|
38
129
|
```typescript
|
|
@@ -92,6 +183,9 @@ export const GET = withMicropay(handler, {
|
|
|
92
183
|
Import only what you need:
|
|
93
184
|
|
|
94
185
|
```typescript
|
|
186
|
+
// 🔥 x402Fetch - Drop-in fetch replacement (NEW!)
|
|
187
|
+
import { createX402Fetch } from '@alleyboss/micropay-solana-x402-paywall/fetch';
|
|
188
|
+
|
|
95
189
|
// Express Middleware
|
|
96
190
|
import { x402Middleware } from '@alleyboss/micropay-solana-x402-paywall/express';
|
|
97
191
|
|
|
@@ -204,6 +298,25 @@ flowchart LR
|
|
|
204
298
|
F --> G((Unlock Data))
|
|
205
299
|
```
|
|
206
300
|
|
|
301
|
+
### ⚡ The Sexy Way (New in v3.5.1)
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { createPayingAgent } from '@alleyboss/micropay-solana-x402-paywall/agent';
|
|
305
|
+
|
|
306
|
+
// One liner - that's it!
|
|
307
|
+
const agent = createPayingAgent(process.env.SOLANA_PRIVATE_KEY!);
|
|
308
|
+
|
|
309
|
+
// Fetch with auto-payment
|
|
310
|
+
const response = await agent.get('https://api.example.com/premium');
|
|
311
|
+
const data = await response.json();
|
|
312
|
+
|
|
313
|
+
// Check balance
|
|
314
|
+
const { sol } = await agent.getBalance();
|
|
315
|
+
console.log(`Agent has ${sol} SOL`);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### The Verbose Way (Full Control)
|
|
319
|
+
|
|
207
320
|
```typescript
|
|
208
321
|
import { executeAgentPayment } from '@alleyboss/micropay-solana-x402-paywall/agent';
|
|
209
322
|
import { Keypair, Connection } from '@solana/web3.js';
|
package/dist/agent/index.cjs
CHANGED
|
@@ -351,8 +351,440 @@ async function getRemainingCredits(token, secret) {
|
|
|
351
351
|
};
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
+
// src/fetch/types.ts
|
|
355
|
+
var X402ErrorCode = {
|
|
356
|
+
/** User rejected the payment */
|
|
357
|
+
USER_REJECTED: "USER_REJECTED",
|
|
358
|
+
/** Insufficient wallet balance */
|
|
359
|
+
INSUFFICIENT_BALANCE: "INSUFFICIENT_BALANCE",
|
|
360
|
+
/** Transaction failed on-chain */
|
|
361
|
+
TRANSACTION_FAILED: "TRANSACTION_FAILED",
|
|
362
|
+
/** Network/RPC error */
|
|
363
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
364
|
+
/** Invalid 402 response format */
|
|
365
|
+
INVALID_402_RESPONSE: "INVALID_402_RESPONSE",
|
|
366
|
+
/** Payment timeout */
|
|
367
|
+
TIMEOUT: "TIMEOUT",
|
|
368
|
+
/** Wallet not connected */
|
|
369
|
+
WALLET_NOT_CONNECTED: "WALLET_NOT_CONNECTED",
|
|
370
|
+
/** Payment amount exceeds maxPaymentPerRequest */
|
|
371
|
+
AMOUNT_EXCEEDS_LIMIT: "AMOUNT_EXCEEDS_LIMIT",
|
|
372
|
+
/** Recipient address not in allowedRecipients whitelist */
|
|
373
|
+
RECIPIENT_NOT_ALLOWED: "RECIPIENT_NOT_ALLOWED",
|
|
374
|
+
/** Rate limit exceeded */
|
|
375
|
+
RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED"
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// src/fetch/errors.ts
|
|
379
|
+
var X402PaymentError = class _X402PaymentError extends Error {
|
|
380
|
+
constructor(message, code, requirements, cause) {
|
|
381
|
+
super(message);
|
|
382
|
+
this.code = code;
|
|
383
|
+
this.requirements = requirements;
|
|
384
|
+
this.cause = cause;
|
|
385
|
+
if (Error.captureStackTrace) {
|
|
386
|
+
Error.captureStackTrace(this, _X402PaymentError);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
name = "X402PaymentError";
|
|
390
|
+
/**
|
|
391
|
+
* Check if error is retryable
|
|
392
|
+
*/
|
|
393
|
+
get isRetryable() {
|
|
394
|
+
const retryableCodes = [
|
|
395
|
+
X402ErrorCode.NETWORK_ERROR,
|
|
396
|
+
X402ErrorCode.TIMEOUT,
|
|
397
|
+
X402ErrorCode.TRANSACTION_FAILED
|
|
398
|
+
];
|
|
399
|
+
return retryableCodes.includes(this.code);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Convert to JSON-serializable object
|
|
403
|
+
*/
|
|
404
|
+
toJSON() {
|
|
405
|
+
return {
|
|
406
|
+
name: this.name,
|
|
407
|
+
message: this.message,
|
|
408
|
+
code: this.code,
|
|
409
|
+
requirements: this.requirements,
|
|
410
|
+
isRetryable: this.isRetryable
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
function userRejectedError(requirements) {
|
|
415
|
+
return new X402PaymentError(
|
|
416
|
+
"User rejected the payment request",
|
|
417
|
+
X402ErrorCode.USER_REJECTED,
|
|
418
|
+
requirements
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
function insufficientBalanceError(requirements, balance) {
|
|
422
|
+
return new X402PaymentError(
|
|
423
|
+
`Insufficient balance: have ${balance} lamports, need ${requirements.amount}`,
|
|
424
|
+
X402ErrorCode.INSUFFICIENT_BALANCE,
|
|
425
|
+
requirements
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
function transactionFailedError(requirements, cause) {
|
|
429
|
+
return new X402PaymentError(
|
|
430
|
+
`Transaction failed: ${cause?.message ?? "Unknown error"}`,
|
|
431
|
+
X402ErrorCode.TRANSACTION_FAILED,
|
|
432
|
+
requirements,
|
|
433
|
+
cause
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
function networkError(cause) {
|
|
437
|
+
return new X402PaymentError(
|
|
438
|
+
`Network error: ${cause?.message ?? "Connection failed"}`,
|
|
439
|
+
X402ErrorCode.NETWORK_ERROR,
|
|
440
|
+
void 0,
|
|
441
|
+
cause
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
function invalid402ResponseError(details) {
|
|
445
|
+
return new X402PaymentError(
|
|
446
|
+
`Invalid 402 response: ${details ?? "Missing or malformed payment requirements"}`,
|
|
447
|
+
X402ErrorCode.INVALID_402_RESPONSE
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
function timeoutError(requirements) {
|
|
451
|
+
return new X402PaymentError(
|
|
452
|
+
"Payment flow timed out",
|
|
453
|
+
X402ErrorCode.TIMEOUT,
|
|
454
|
+
requirements
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
function walletNotConnectedError() {
|
|
458
|
+
return new X402PaymentError(
|
|
459
|
+
"Wallet is not connected",
|
|
460
|
+
X402ErrorCode.WALLET_NOT_CONNECTED
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
function amountExceedsLimitError(requirements, limit) {
|
|
464
|
+
return new X402PaymentError(
|
|
465
|
+
`Payment amount ${requirements.amount} exceeds limit of ${limit} lamports`,
|
|
466
|
+
X402ErrorCode.AMOUNT_EXCEEDS_LIMIT,
|
|
467
|
+
requirements
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
function recipientNotAllowedError(requirements, recipient) {
|
|
471
|
+
return new X402PaymentError(
|
|
472
|
+
`Recipient ${recipient} is not in the allowed recipients list`,
|
|
473
|
+
X402ErrorCode.RECIPIENT_NOT_ALLOWED,
|
|
474
|
+
requirements
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
function rateLimitExceededError(limit, windowMs) {
|
|
478
|
+
return new X402PaymentError(
|
|
479
|
+
`Rate limit exceeded: max ${limit} payments per ${windowMs / 1e3}s`,
|
|
480
|
+
X402ErrorCode.RATE_LIMIT_EXCEEDED
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/shared/constants.ts
|
|
485
|
+
var RPC_ENDPOINTS = {
|
|
486
|
+
"mainnet-beta": "https://api.mainnet-beta.solana.com",
|
|
487
|
+
"devnet": "https://api.devnet.solana.com",
|
|
488
|
+
"testnet": "https://api.testnet.solana.com"
|
|
489
|
+
};
|
|
490
|
+
var DEFAULT_CONFIRMATION_TIMEOUT = 3e4;
|
|
491
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
492
|
+
var DEFAULT_RATE_LIMIT_WINDOW_MS = 6e4;
|
|
493
|
+
var DEFAULT_RATE_LIMIT_MAX_PAYMENTS = 10;
|
|
494
|
+
|
|
495
|
+
// src/fetch/x402Fetch.ts
|
|
496
|
+
function isKeypair(wallet) {
|
|
497
|
+
return wallet instanceof web3_js.Keypair;
|
|
498
|
+
}
|
|
499
|
+
function isWalletConnected(wallet) {
|
|
500
|
+
if (isKeypair(wallet)) return true;
|
|
501
|
+
return wallet.connected && wallet.publicKey != null;
|
|
502
|
+
}
|
|
503
|
+
function getPublicKey(wallet) {
|
|
504
|
+
if (isKeypair(wallet)) return wallet.publicKey;
|
|
505
|
+
if (!wallet.publicKey) throw walletNotConnectedError();
|
|
506
|
+
return wallet.publicKey;
|
|
507
|
+
}
|
|
508
|
+
function parse402Response(response) {
|
|
509
|
+
const x402Header = response.headers.get("X-Payment-Requirements");
|
|
510
|
+
if (x402Header) {
|
|
511
|
+
try {
|
|
512
|
+
const parsed = JSON.parse(x402Header);
|
|
513
|
+
return {
|
|
514
|
+
payTo: parsed.payTo ?? parsed.recipient,
|
|
515
|
+
amount: String(parsed.amount),
|
|
516
|
+
asset: parsed.asset ?? "SOL",
|
|
517
|
+
network: parsed.network ?? "solana-mainnet",
|
|
518
|
+
description: parsed.description,
|
|
519
|
+
resource: parsed.resource,
|
|
520
|
+
maxAge: parsed.maxAge
|
|
521
|
+
};
|
|
522
|
+
} catch {
|
|
523
|
+
throw invalid402ResponseError("Invalid X-Payment-Requirements header");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const wwwAuth = response.headers.get("WWW-Authenticate");
|
|
527
|
+
if (wwwAuth?.startsWith("X402")) {
|
|
528
|
+
try {
|
|
529
|
+
const base64Part = wwwAuth.slice(5).trim();
|
|
530
|
+
const jsonStr = atob(base64Part);
|
|
531
|
+
const parsed = JSON.parse(jsonStr);
|
|
532
|
+
return {
|
|
533
|
+
payTo: parsed.payTo ?? parsed.recipient,
|
|
534
|
+
amount: String(parsed.amount),
|
|
535
|
+
asset: parsed.asset ?? "SOL",
|
|
536
|
+
network: parsed.network ?? "solana-mainnet",
|
|
537
|
+
description: parsed.description,
|
|
538
|
+
resource: parsed.resource,
|
|
539
|
+
maxAge: parsed.maxAge
|
|
540
|
+
};
|
|
541
|
+
} catch {
|
|
542
|
+
throw invalid402ResponseError("Invalid WWW-Authenticate header");
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
throw invalid402ResponseError("No payment requirements found in 402 response");
|
|
546
|
+
}
|
|
547
|
+
function buildPaymentHeader(signature) {
|
|
548
|
+
const payload = {
|
|
549
|
+
x402Version: 2,
|
|
550
|
+
scheme: "exact",
|
|
551
|
+
payload: { signature }
|
|
552
|
+
};
|
|
553
|
+
return `X402 ${btoa(JSON.stringify(payload))}`;
|
|
554
|
+
}
|
|
555
|
+
function createX402Fetch(config) {
|
|
556
|
+
const {
|
|
557
|
+
wallet,
|
|
558
|
+
network = "mainnet-beta",
|
|
559
|
+
connection: providedConnection,
|
|
560
|
+
// Reserved for future facilitator integration
|
|
561
|
+
onPaymentRequired,
|
|
562
|
+
onPaymentSuccess,
|
|
563
|
+
onPaymentError,
|
|
564
|
+
priorityFee,
|
|
565
|
+
maxRetries = DEFAULT_MAX_RETRIES,
|
|
566
|
+
timeout = DEFAULT_CONFIRMATION_TIMEOUT,
|
|
567
|
+
// Security options
|
|
568
|
+
maxPaymentPerRequest,
|
|
569
|
+
allowedRecipients,
|
|
570
|
+
// UX options
|
|
571
|
+
commitment = "confirmed",
|
|
572
|
+
rateLimit
|
|
573
|
+
} = config;
|
|
574
|
+
const paymentTimestamps = [];
|
|
575
|
+
const rateLimitMax = rateLimit?.maxPayments ?? DEFAULT_RATE_LIMIT_MAX_PAYMENTS;
|
|
576
|
+
const rateLimitWindow = rateLimit?.windowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS;
|
|
577
|
+
function checkRateLimit() {
|
|
578
|
+
const now = Date.now();
|
|
579
|
+
while (paymentTimestamps.length > 0 && paymentTimestamps[0] < now - rateLimitWindow) {
|
|
580
|
+
paymentTimestamps.shift();
|
|
581
|
+
}
|
|
582
|
+
if (paymentTimestamps.length >= rateLimitMax) {
|
|
583
|
+
throw rateLimitExceededError(rateLimitMax, rateLimitWindow);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function recordPayment() {
|
|
587
|
+
paymentTimestamps.push(Date.now());
|
|
588
|
+
}
|
|
589
|
+
function validateSecurityRequirements(requirements) {
|
|
590
|
+
const amountLamports = BigInt(requirements.amount);
|
|
591
|
+
if (maxPaymentPerRequest !== void 0 && amountLamports > maxPaymentPerRequest) {
|
|
592
|
+
throw amountExceedsLimitError(requirements, maxPaymentPerRequest);
|
|
593
|
+
}
|
|
594
|
+
if (allowedRecipients !== void 0 && allowedRecipients.length > 0) {
|
|
595
|
+
if (!allowedRecipients.includes(requirements.payTo)) {
|
|
596
|
+
throw recipientNotAllowedError(requirements, requirements.payTo);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const connection = providedConnection ?? new web3_js.Connection(RPC_ENDPOINTS[network], {
|
|
601
|
+
commitment
|
|
602
|
+
});
|
|
603
|
+
async function executePayment(requirements) {
|
|
604
|
+
const payer = getPublicKey(wallet);
|
|
605
|
+
const recipient = new web3_js.PublicKey(requirements.payTo);
|
|
606
|
+
const amountLamports = BigInt(requirements.amount);
|
|
607
|
+
const balance = await connection.getBalance(payer);
|
|
608
|
+
if (BigInt(balance) < amountLamports) {
|
|
609
|
+
throw insufficientBalanceError(requirements, BigInt(balance));
|
|
610
|
+
}
|
|
611
|
+
const instructions = [];
|
|
612
|
+
if (priorityFee?.enabled) {
|
|
613
|
+
instructions.push(
|
|
614
|
+
web3_js.ComputeBudgetProgram.setComputeUnitPrice({
|
|
615
|
+
microLamports: priorityFee.microLamports ?? 5e3
|
|
616
|
+
})
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
instructions.push(
|
|
620
|
+
web3_js.SystemProgram.transfer({
|
|
621
|
+
fromPubkey: payer,
|
|
622
|
+
toPubkey: recipient,
|
|
623
|
+
lamports: amountLamports
|
|
624
|
+
})
|
|
625
|
+
);
|
|
626
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
627
|
+
const messageV0 = new web3_js.TransactionMessage({
|
|
628
|
+
payerKey: payer,
|
|
629
|
+
recentBlockhash: blockhash,
|
|
630
|
+
instructions
|
|
631
|
+
}).compileToV0Message();
|
|
632
|
+
const tx = new web3_js.VersionedTransaction(messageV0);
|
|
633
|
+
if (isKeypair(wallet)) {
|
|
634
|
+
tx.sign([wallet]);
|
|
635
|
+
} else {
|
|
636
|
+
const signerWallet = wallet;
|
|
637
|
+
if (!signerWallet.signTransaction) {
|
|
638
|
+
throw new X402PaymentError(
|
|
639
|
+
"Wallet does not support transaction signing. Use a SignerWalletAdapter.",
|
|
640
|
+
"WALLET_NOT_CONNECTED"
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
const signedTx = await signerWallet.signTransaction(tx);
|
|
644
|
+
if (signedTx.signatures[0]) {
|
|
645
|
+
tx.signatures[0] = signedTx.signatures[0];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
const signature = await connection.sendTransaction(tx, {
|
|
649
|
+
maxRetries
|
|
650
|
+
});
|
|
651
|
+
await connection.confirmTransaction({
|
|
652
|
+
signature,
|
|
653
|
+
blockhash,
|
|
654
|
+
lastValidBlockHeight
|
|
655
|
+
}, commitment);
|
|
656
|
+
return signature;
|
|
657
|
+
}
|
|
658
|
+
async function x402Fetch(input, init) {
|
|
659
|
+
const { skipPayment, paymentOverride, ...fetchInit } = init ?? {};
|
|
660
|
+
let response;
|
|
661
|
+
try {
|
|
662
|
+
response = await fetch(input, fetchInit);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
throw networkError(error instanceof Error ? error : void 0);
|
|
665
|
+
}
|
|
666
|
+
if (response.status !== 402) {
|
|
667
|
+
return response;
|
|
668
|
+
}
|
|
669
|
+
if (skipPayment) {
|
|
670
|
+
return response;
|
|
671
|
+
}
|
|
672
|
+
if (!isWalletConnected(wallet)) {
|
|
673
|
+
throw walletNotConnectedError();
|
|
674
|
+
}
|
|
675
|
+
let requirements;
|
|
676
|
+
try {
|
|
677
|
+
requirements = parse402Response(response);
|
|
678
|
+
if (paymentOverride) {
|
|
679
|
+
requirements = { ...requirements, ...paymentOverride };
|
|
680
|
+
}
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (error instanceof X402PaymentError) throw error;
|
|
683
|
+
throw invalid402ResponseError(error instanceof Error ? error.message : void 0);
|
|
684
|
+
}
|
|
685
|
+
validateSecurityRequirements(requirements);
|
|
686
|
+
checkRateLimit();
|
|
687
|
+
if (onPaymentRequired) {
|
|
688
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
689
|
+
const shouldProceed = await onPaymentRequired(requirements, url);
|
|
690
|
+
if (!shouldProceed) {
|
|
691
|
+
throw userRejectedError(requirements);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
let signature;
|
|
695
|
+
try {
|
|
696
|
+
const paymentPromise = executePayment(requirements);
|
|
697
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
698
|
+
setTimeout(() => reject(timeoutError(requirements)), timeout);
|
|
699
|
+
});
|
|
700
|
+
signature = await Promise.race([paymentPromise, timeoutPromise]);
|
|
701
|
+
recordPayment();
|
|
702
|
+
if (onPaymentSuccess) {
|
|
703
|
+
await onPaymentSuccess(signature, requirements);
|
|
704
|
+
}
|
|
705
|
+
} catch (error) {
|
|
706
|
+
if (error instanceof X402PaymentError) {
|
|
707
|
+
if (onPaymentError) {
|
|
708
|
+
await onPaymentError(error, requirements);
|
|
709
|
+
}
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
const wrappedError = transactionFailedError(
|
|
713
|
+
requirements,
|
|
714
|
+
error instanceof Error ? error : void 0
|
|
715
|
+
);
|
|
716
|
+
if (onPaymentError) {
|
|
717
|
+
await onPaymentError(wrappedError, requirements);
|
|
718
|
+
}
|
|
719
|
+
throw wrappedError;
|
|
720
|
+
}
|
|
721
|
+
const retryHeaders = new Headers(fetchInit?.headers);
|
|
722
|
+
retryHeaders.set("Authorization", buildPaymentHeader(signature));
|
|
723
|
+
try {
|
|
724
|
+
return await fetch(input, {
|
|
725
|
+
...fetchInit,
|
|
726
|
+
headers: retryHeaders
|
|
727
|
+
});
|
|
728
|
+
} catch (error) {
|
|
729
|
+
throw networkError(error instanceof Error ? error : void 0);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return x402Fetch;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// src/agent/payingAgent.ts
|
|
736
|
+
function createPayingAgent(privateKey, config = {}) {
|
|
737
|
+
const {
|
|
738
|
+
network = "mainnet-beta",
|
|
739
|
+
rpcUrl,
|
|
740
|
+
maxPaymentPerRequest,
|
|
741
|
+
allowedRecipients,
|
|
742
|
+
priorityFee = true
|
|
743
|
+
} = config;
|
|
744
|
+
let keypair;
|
|
745
|
+
if (privateKey.includes(",")) {
|
|
746
|
+
const bytes = new Uint8Array(privateKey.split(",").map((n) => parseInt(n.trim(), 10)));
|
|
747
|
+
keypair = web3_js.Keypair.fromSecretKey(bytes);
|
|
748
|
+
} else {
|
|
749
|
+
keypair = web3_js.Keypair.fromSecretKey(bs58__default.default.decode(privateKey));
|
|
750
|
+
}
|
|
751
|
+
const endpoint = rpcUrl ?? RPC_ENDPOINTS[network];
|
|
752
|
+
const connection = new web3_js.Connection(endpoint, "confirmed");
|
|
753
|
+
const fetchConfig = {
|
|
754
|
+
wallet: keypair,
|
|
755
|
+
network,
|
|
756
|
+
connection,
|
|
757
|
+
maxPaymentPerRequest,
|
|
758
|
+
allowedRecipients,
|
|
759
|
+
priorityFee: priorityFee ? { enabled: true, microLamports: 5e3 } : void 0
|
|
760
|
+
};
|
|
761
|
+
const x402Fetch = createX402Fetch(fetchConfig);
|
|
762
|
+
return {
|
|
763
|
+
get: (url, init) => x402Fetch(url, { ...init, method: "GET" }),
|
|
764
|
+
post: (url, body, init) => x402Fetch(url, {
|
|
765
|
+
...init,
|
|
766
|
+
method: "POST",
|
|
767
|
+
body: JSON.stringify(body),
|
|
768
|
+
headers: {
|
|
769
|
+
"Content-Type": "application/json",
|
|
770
|
+
...init?.headers
|
|
771
|
+
}
|
|
772
|
+
}),
|
|
773
|
+
fetch: (url, init) => x402Fetch(url, init),
|
|
774
|
+
publicKey: keypair.publicKey.toBase58(),
|
|
775
|
+
getBalance: async () => {
|
|
776
|
+
const lamports = await connection.getBalance(keypair.publicKey);
|
|
777
|
+
return {
|
|
778
|
+
lamports: BigInt(lamports),
|
|
779
|
+
sol: lamports / 1e9
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
354
785
|
exports.addCredits = addCredits;
|
|
355
786
|
exports.createCreditSession = createCreditSession;
|
|
787
|
+
exports.createPayingAgent = createPayingAgent;
|
|
356
788
|
exports.executeAgentPayment = executeAgentPayment;
|
|
357
789
|
exports.generateAgentKeypair = generateAgentKeypair;
|
|
358
790
|
exports.getAgentBalance = getAgentBalance;
|
package/dist/agent/index.d.cts
CHANGED
|
@@ -218,4 +218,80 @@ declare function getRemainingCredits(token: string, secret: string): Promise<{
|
|
|
218
218
|
bundleExpiry?: number;
|
|
219
219
|
}>;
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Supported Solana networks
|
|
223
|
+
*/
|
|
224
|
+
declare const SOLANA_NETWORKS: readonly ["mainnet-beta", "devnet", "testnet"];
|
|
225
|
+
type SolanaNetwork = (typeof SOLANA_NETWORKS)[number];
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @fileoverview Shaw-style Agent Helper
|
|
229
|
+
* The sexiest way to create a paying AI agent
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* import { createPayingAgent } from '@alleyboss/micropay-solana-x402-paywall/agent';
|
|
234
|
+
*
|
|
235
|
+
* const agent = createPayingAgent(process.env.SOLANA_PRIVATE_KEY!);
|
|
236
|
+
* const data = await agent.get('https://api.example.com/premium');
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Configuration for creating a paying agent
|
|
242
|
+
*/
|
|
243
|
+
interface PayingAgentConfig {
|
|
244
|
+
/** Network: 'mainnet-beta' | 'devnet' | 'testnet' */
|
|
245
|
+
network?: SolanaNetwork;
|
|
246
|
+
/** Custom RPC URL */
|
|
247
|
+
rpcUrl?: string;
|
|
248
|
+
/** Max payment per request in lamports (safety limit) */
|
|
249
|
+
maxPaymentPerRequest?: bigint;
|
|
250
|
+
/** Allowed recipient addresses (whitelist) */
|
|
251
|
+
allowedRecipients?: string[];
|
|
252
|
+
/** Enable priority fees for faster confirmation */
|
|
253
|
+
priorityFee?: boolean;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Paying agent interface - fetch-like methods with auto-payment
|
|
257
|
+
*/
|
|
258
|
+
interface PayingAgent {
|
|
259
|
+
/** GET request with auto-payment */
|
|
260
|
+
get: (url: string, init?: RequestInit) => Promise<Response>;
|
|
261
|
+
/** POST request with auto-payment */
|
|
262
|
+
post: (url: string, body: unknown, init?: RequestInit) => Promise<Response>;
|
|
263
|
+
/** Generic fetch with auto-payment */
|
|
264
|
+
fetch: (url: string, init?: RequestInit) => Promise<Response>;
|
|
265
|
+
/** Get the agent's public key */
|
|
266
|
+
publicKey: string;
|
|
267
|
+
/** Get the agent's balance */
|
|
268
|
+
getBalance: () => Promise<{
|
|
269
|
+
lamports: bigint;
|
|
270
|
+
sol: number;
|
|
271
|
+
}>;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Create a paying AI agent - the sexiest one-liner for agent developers
|
|
275
|
+
*
|
|
276
|
+
* @param privateKey - Base58 or comma-separated Uint8Array string
|
|
277
|
+
* @param config - Optional configuration
|
|
278
|
+
* @returns PayingAgent with fetch-like methods
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* const agent = createPayingAgent(process.env.SOLANA_PRIVATE_KEY!);
|
|
283
|
+
*
|
|
284
|
+
* // Simple GET
|
|
285
|
+
* const response = await agent.get('https://api.example.com/premium');
|
|
286
|
+
*
|
|
287
|
+
* // POST with body
|
|
288
|
+
* const result = await agent.post('https://api.example.com/ai', { prompt: 'Hello' });
|
|
289
|
+
*
|
|
290
|
+
* // Check balance
|
|
291
|
+
* const { sol } = await agent.getBalance();
|
|
292
|
+
* console.log(`Agent has ${sol} SOL`);
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
declare function createPayingAgent(privateKey: string, config?: PayingAgentConfig): PayingAgent;
|
|
296
|
+
|
|
297
|
+
export { type AgentPaymentResult, type CreditSessionClaims, type CreditSessionConfig, type CreditSessionData, type CreditValidation, type ExecuteAgentPaymentParams, type PayingAgent, type PayingAgentConfig, type UseCreditResult, addCredits, createCreditSession, createPayingAgent, executeAgentPayment, generateAgentKeypair, getAgentBalance, getRemainingCredits, hasAgentSufficientBalance, keypairFromBase58, useCredit, validateCreditSession };
|